package top.usking.tools.log.interceptor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.usking.tools.log.config.CustomerLogService;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Output log method interceptor implement the {@link MethodInterceptor}.
 *
 * @author LiuShijin
 * @date 2022-01-28
 */
public class OutputLogMethodInterceptor implements MethodInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(OutputLogMethodInterceptor.class);
    private ThreadLocal<AtomicInteger> COUNTER = ThreadLocal.withInitial(() -> new AtomicInteger(0));

    private ObjectMapper objectMapper = new ObjectMapper();
    private final CustomerLogService customerLogService;

    public OutputLogMethodInterceptor(CustomerLogService customerLogService) {
        this.customerLogService = customerLogService;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        if (customerLogService.isEnabled()) {
            int andIncrement = COUNTER.get().getAndIncrement();
            Method method = invocation.getMethod();
            if (method.getDeclaringClass().isAnnotationPresent(RestController.class)
                    || method.getDeclaringClass().isAnnotationPresent(Controller.class)) {
                String requestPath = getPath();
                LOGGER.info("{}---------Path: {}", getSpace(andIncrement), requestPath);
            }
            String className = method.getDeclaringClass().getName();
            String returnType = method.getReturnType().getSimpleName();
            String methodName = method.getName();
            LOGGER.info("{}---------Class: {}", getSpace(andIncrement), className);
            LOGGER.info("{}---------Method: public {} {}({})", getSpace(andIncrement), returnType, methodName, getArgs(invocation));
        }
        Object proceed = invocation.proceed();
        if (customerLogService.isEnabled()) {
            int andDecrement = COUNTER.get().getAndDecrement() - 1;
            if (customerLogService.isWithJson()) {
                LOGGER.info("{}---------Returns: {}", getSpace(andDecrement), objectMapper.writeValueAsString(proceed));
            } else {
                LOGGER.info("{}---------Returns: {}", getSpace(andDecrement), proceed);
            }
        }
        if (COUNTER.get().get() == 0) {
            COUNTER.remove();
        }
        return proceed;
    }

    /**
     * Get path from {@link RequestContextHolder}.
     * This path includes query string.
     *
     * @return The string of path.
     */
    private String getPath() {
        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if (Objects.isNull(request.getQueryString())) {
            return request.getRequestURI();
        }
        return request.getRequestURI() + "?" + request.getQueryString();
    }

    /**
     * Get params.
     *
     * @param invocation The method invocation.
     * @return The string of parameters.
     */
    private String getArgs(MethodInvocation invocation) {
        Parameter[] parameters = invocation.getMethod().getParameters();
        Object[] arguments = invocation.getArguments();
        StringBuilder builder = new StringBuilder();
        boolean isFirst = true;
        for (int i = 0; i < parameters.length; i++) {
            if (!isFirst) {
                builder.append(",");
            } else {
                isFirst = false;
            }
            builder.append(parameters[i].getType().getSimpleName());
            builder.append(" ");
            builder.append(parameters[i].getName());
            builder.append("=");
            Object argument = arguments[i];
            if (Objects.nonNull(argument)) {
                if (customerLogService.isWithJson()) {
                    try {
                        builder.append(objectMapper.writeValueAsString(argument));
                    } catch (JsonProcessingException e) {
                        builder.append(argument);
                        LOGGER.error("Json processing exception. {}" + e.getMessage());
                    }
                } else {
                    builder.append(argument);
                }
            }
        }
        return builder.toString();
    }

    private static String getSpace(int count) {
        if (count <= 0) {
            return "";
        }
        StringBuilder builder = new StringBuilder(count * 4);
        for (int i = 0; i < count; i++) {
            builder.append("    ");
        }
        return builder.toString();
    }
}
